package coderx

import (
	"gitee.com/zhongguo168a/gocodes/datax/mapx"
	"gitee.com/zhongguo168a/gocodes/datax/reflectx"
	"gitee.com/zhongguo168a/gocodes/datax/schemax"
	"gitee.com/zhongguo168a/gocodes/datax/schemax/basickind"
	"gitee.com/zhongguo168a/gocodes/myx/errorx"
	"strconv"
)

func NewMapToRef() (obj *MapToRef) {
	obj = &MapToRef{}
	return
}

type MapToRef struct {
	//
	source map[string]interface{}

	// 忽视空值
	// 空值不会输出到map中
	ignoreNil bool
}

func (coder *MapToRef) Reset() {
	coder.source = nil
	coder.ignoreNil = false
}

func (coder *MapToRef) SetSource(source map[string]interface{}) *MapToRef {
	coder.source = source
	return coder
}

// Create 通过map创建一个对象
// map必须设置 key=_type value=string， value指向schemax注册的声明
func (coder *MapToRef) Create() (obj reflectx.IRefObject, err error) {
	_type := mapx.String(coder.source, "_type")
	if _type == "" {
		err = errorx.New("_type not found")
		return
	}
	schema := schemax.GetDeclByKey(_type)
	if schema == nil {
		err = errorx.New("schema not found: " + _type)
		return
	}

	decl, ok := schema.(*schemax.ClassDecl)
	if !ok {
		err = errorx.New("decl not schemax.ClassDecl: " + _type)
		return
	}

	reftype := reflectx.GetType(_type)
	if reftype == nil {
		err = errorx.New("reftype not found: " + decl.RefType())
		return
	}
	obj = reftype.RefNew()
	err = coder.fromMapObj(reftype, obj, coder.source, schema, "")
	if err != nil {
		err = errorx.Wrap(err, _type)
		return
	}
	return
}

func (coder *MapToRef) Write(obj reflectx.IRefObject) (err error) {
	schema := schemax.GetDeclByKey(obj.RefType())
	if schema == nil {
		return errorx.New("schema not found: " + obj.RefType())
	}
	reftype := reflectx.GetType(obj.RefType())
	if reftype == nil {
		err = errorx.New("reftype not found: " + obj.RefType())
		return
	}
	return coder.fromMapObj(reftype, obj, coder.source, schema, "")
}

// keyMode 0-source的字段, 1-所有字段
func (coder *MapToRef) fromMapObj(reftype reflectx.IRefType, obj reflectx.IRefObject, smap map[string]interface{}, schema schemax.IDecl, paths string) (err error) {
	var (
		decl = schema.(*schemax.ClassDecl)
	)

	allfields := decl.GetAllField()
	for _, field := range allfields {
		fname := field.Name
		alias := field.Alias()

		mval, mhas := smap[alias]
		if !mhas {
			continue
		}
		fname = alias

		switch ftyp := field.Type.(type) {
		case *schemax.ClassType:
			oval, isNil := reftype.(reflectx.IRefField).RefGet(obj, alias)
			st, classerr := coder.fromMapClass(reftype, oval.(reflectx.IRefObject), isNil, mval, ftyp.Decl, paths+"/"+fname)
			if classerr != nil {
				return classerr
			}
			reftype.(reflectx.IRefField).RefSet(obj, alias, st)
		case *schemax.ArrayType:
			err = coder.fromMapSlice(reftype.(reflectx.IRefSlice), obj, mval, fname, ftyp, paths+"/"+fname)
			if err != nil {
				return
			}
		case *schemax.MapType:
			err = coder.fromMapMap(reftype.(reflectx.IRefMap), obj, fname, mval, ftyp, paths+"/"+fname)
			if err != nil {
				return
			}
		case *schemax.EnumType:
			st, classerr := coder.fromMapEnum(mval, ftyp.Decl, paths+"/"+fname)
			if classerr != nil {
				return classerr
			}
			reftype.(reflectx.IRefField).RefSet(obj, field.Name, st)
		case *schemax.BasicType:
			reftype.(reflectx.IRefField).RefSet(obj, field.Name, coder.fromMapBasic(mval, ftyp))
		case *schemax.AnyType:
			oval, isNil := reftype.(reflectx.IRefField).RefGet(obj, fname)
			st, classerr := coder.fromMapAny(reftype, oval, isNil, mval, paths+"/"+fname)
			if classerr != nil {
				return classerr
			}
			reftype.(reflectx.IRefField).RefSet(obj, fname, st)
		default:
		}
	}
	return
}

func (coder *MapToRef) fromMapAny(reftype reflectx.IRefType, oany interface{}, isNil bool, sany interface{}, paths string) (st interface{}, err error) {
	var (
		decl  string
		smap  map[string]interface{}
		ismap bool
		oval  reflectx.IRefObject
	)
	if sany == nil { // 返回，并设置obj为nil
		return
	}

	if oany == nil {
		smap, ismap = sany.(map[string]interface{})
		if ismap == false {
			return
		}
		decl = mapx.String(smap, "_type")
		if decl == "" {
			st = sany
			return
		}
	} else {
		oval = oany.(reflectx.IRefObject)
		decl = oval.RefType()
	}

	st, err = coder.fromMapClass(reftype, oval, isNil, sany.(map[string]interface{}), decl, paths)
	if err != nil {
		return
	}
	return
}

type TestClass1 struct {
}

func (t *TestClass1) RefType() string {
	panic("implement me")
}

func (coder *MapToRef) fromMapClass(reftype reflectx.IRefType, oval reflectx.IRefObject, isNil bool, many interface{}, decl string, paths string) (rval reflectx.IRefObject, err error) {
	if many == nil { // 设置为nil
		return nil, nil
	}

	if isNil {
		rval, err = reflectx.NewObject(decl)
		if err != nil {
			err = errorx.New(paths + ": new object " + decl + " : " + err.Error())
			return
		}
	} else {
		rval = oval
	}
	maptype := reflectx.GetType(decl)
	if maptype == nil {
		err = errorx.New(paths + ": reftype not found: " + oval.RefType())
		return
	}

	stdesc := schemax.GetDeclByKey(decl)
	err = coder.fromMapObj(maptype, rval, many.(map[string]interface{}), stdesc, paths)
	return
}
func (coder *MapToRef) fromMapEnum(mval interface{}, decl string, paths string) (st interface{}, err error) {
	idesc := schemax.GetDeclByKey(decl)
	if idesc == nil {
		err = errorx.New(paths + ": enum schema not found: " + decl)
		return
	}

	enumdesc := idesc.(*schemax.EnumDecl)
	st = enumdesc.ConvertToValue(mval)
	return
}

func (coder *MapToRef) fromMapSlice(reftype reflectx.IRefSlice, obj reflectx.IRefObject, many interface{}, fname string, ftyp *schemax.ArrayType, paths string) (err error) {
	if many == nil {
		reftype.RefSet(obj, fname, reftype.RefSliceNew(fname, 0, 0))
		return
	}

	marr := many.([]interface{})
	mlen := len(marr)
	mcap := cap(marr)
	reftype.RefSet(obj, fname, reftype.RefSliceNew(fname, mlen, mcap))
	if mlen == 0 {
		return
	}
	switch etyp := ftyp.Elem.(type) {
	case *schemax.ClassType:
		for i := 0; i < len(marr); i++ {
			mval := marr[i]
			oval, isNil := reftype.RefSliceGet(obj, fname, i)
			st, classerr := coder.fromMapClass(reftype, oval.(reflectx.IRefObject), isNil, mval, etyp.Decl, paths+"/"+strconv.Itoa(i))
			if classerr != nil {
				return classerr
			}
			reftype.RefSliceSet(obj, fname, i, st)
		}
	case *schemax.BasicType:
		for i := 0; i < len(marr); i++ {
			reftype.RefSliceSet(obj, fname, i, coder.fromMapBasic(marr[i], etyp))
		}
	case *schemax.EnumType:
		for i := 0; i < len(marr); i++ {
			mval := marr[i]
			rval, classerr := coder.fromMapEnum(mval, etyp.Decl, paths+"/"+strconv.Itoa(i))
			if classerr != nil {
				return classerr
			}
			reftype.RefSliceSet(obj, fname, i, rval)
		}
	case *schemax.AnyType:

	default:
		err = errorx.New("decode array: not support type: " + etyp.String())
		return

	}

	return
}

func (coder *MapToRef) fromMapMap(reftype reflectx.IRefMap, obj reflectx.IRefObject, fname string, many interface{}, ftyp *schemax.MapType, paths string) (err error) {
	if many == nil { // 如果设置了nil
		reftype.RefSet(obj, fname, nil)
		return
	}

	mmap := many.(map[string]interface{})

	_, isNil := reftype.RefGet(obj, fname)
	if isNil {
		// 创建
		reftype.RefSet(obj, fname, reftype.RefMapNew(fname))
	}
	//
	switch etyp := ftyp.Value.(type) {
	case *schemax.ClassType:
		for mkey, mval := range mmap {
			oval, onil := reftype.RefMapGet(obj, fname, mkey)
			st, classerr := coder.fromMapClass(reftype, oval.(reflectx.IRefObject), onil, mval, etyp.Decl, paths+"/"+mkey)
			if classerr != nil {
				return classerr
			}
			reftype.RefMapSet(obj, fname, mkey, st)
		}
	case *schemax.BasicType:
		for mkey, mval := range mmap {
			reftype.RefMapSet(obj, fname, mkey, coder.fromMapBasic(mval, etyp))
		}
	case *schemax.EnumType:
		for mkey, mval := range mmap {
			rval, classerr := coder.fromMapEnum(mval, etyp.Decl, paths+"/"+mkey)
			if classerr != nil {
				return classerr
			}
			reftype.RefMapSet(obj, fname, mkey, rval)
		}
	default:
		err = errorx.New(paths + ": decode array: not support type: " + etyp.String())
		return

	}

	return
}

func (coder *MapToRef) fromMapBasic(mval interface{}, ftyp *schemax.BasicType) interface{} {
	return basickind.ConvertKindToStruct(ftyp.Kind, mval)
}
